iT邦幫忙

2022 iThome 鐵人賽

DAY 3
0
Mobile Development

通徹 Flutter 學習路徑系列 第 3

通徹 Flutter 學習路徑 Day 03 - Flutter Architecture 介紹

  • 分享至 

  • xImage
  •  

由於今天老朋友來訪
只能偷偷拿舊文章補救一下
文章內容主要來自官網對於 Flutter Architecture 上的描述
後續再做修正與調整
那一樣開始今天的內容吧


Widget State

大多數Widget的狀態是不會改變的,而這些會屬於StatelessWidget
然而如果有需要進行內容的變動時,則此時這個Widget就會是stateful
ex. 使用者點擊按鈕而改變Widget所表示的數值,此時則需要重建並更新UI
此時Widgets屬於StatefulWidget

State management

State management

為了傳遞 State 的方便這裡提出了 InheritedWidget 可成為共享資料的祖先
由上圖可知,此時 ExamListWidgetGradeWidget 可同時從 StudentState 取得資料

final studentState = StudentState.of(context);

of(content)將回傳距離當前widget最近的祖先節點
並且符合 StudentState這個資料型別
可推論若單純只是要找上面的節點可透過 Widget.of(context) 來取得
而 Flutter 在 InheritedWidget 裡提供 updateShouldNotify() 方法
Flutter 將透過它去得知狀態更新時需呼叫哪個子節點的 Widget
picture1
透過 InheritedWidget 可達到節點下面Theme資料一致的功能
picture2

而目前相關的套件諸如

  1. Provider (多數App使用的)
  2. flutter_hook 官方使用在某些地方(同 React Hook )

Flutter’s rending model

這個部分則仰賴 Skia
(由 C/C++ 所撰寫的圖形引擎,將呼叫 CPU 或 GPU 進行圖形繪製)
大多數跨平台框架會建立虛擬層在 Andriod 及 iOS 的UI繪製層上
後續再由直譯式語言如 JavaSript 來互動並繪製UI
而當使用者進行大量UI操作時,則可能會產生許多的負擔
而相對的 Flutter 繞過原先系統提供UI程式庫
而是透過自己 Widget Set 來進行繪製
Dart 將其編譯成由 Skia 繪製的原生代碼
Flutter允許開發者升級他們的APP持續具備最新的效能增長


From user input to the GPU

以上為Flutter的渲染流程

Build

Build
首先建置的過程中會先產生出 Widgets 樹
而觀察 Container 原始碼則會發現它會由很多的Widgets共同組成
因此在Debug工具中所看到的樹狀架構才會如此的深
然而 Widgets 樹在渲染前會被轉化成 Element Tree
其中便有以下兩種

  • ComponentElement 其他 Widget 的Host
  • RenderObjectElement 參與 Layout 及 Paint 階段的Element

雖然Widgets Objects會被重新建立出來
但在的渲染主要參照的部分則會透過Elements Tree
因此Flutter會透過Walk through Widgets Tree 確保有修改的部分進行重新建立即可

Layout and rendering

Layout and rendering
RenderObject 是所有 Render Tree 的基本類別,定義了抽象的Model用於 Layout 及 Painting
在 Build 階段 Flutter 會對每個 RenderObjectElement 創建一個繼承於 RenderObject

  • RenderParagraph 負責渲染文字(Text)
  • RenderImage 負責圖片 (Image) 渲染
  • RenderTransform 負責轉變在渲染底下的子節點之前,如: 旋轉、動畫呈現

大多的 Flutter 元件透過繼承自 RenderBox 來進行渲染
RenderBox 會提供基本的box constraint model,用來建立渲染物件的最大最小長寬
Flutter 進行Layout時,會透過DFS尋訪Render Tree節點並且由上至下將size constraint傳遞下來
在渲染時,子節點必須符合父節點所給予的限制條件
而子節點會向父節點傳遞在限制下建立時所設定的size
在走訪完成後 每個物件便準備好透過paint()來進行繪製了

Box constraint model layout object in O(n) time

  • 父節點可以透過設定最大最小直為同樣數值來設定子節點的大小。
  • 父節點可單純限制寬度而給長度彈性空間(反之亦然),如: Flow Text (當文字過長自動換行)
    Box constraint

在需要根據限制大小進行畫面設計時,便可透過這個方式來設定
而所有的 RenderObject 的最上層Root為 RenderView
它則代表總體 Render Tree 的結果
當有新的畫面要被Render時,會呼叫 compositeFrame()
在呼叫後則會產生 SceneNuilder 去更新場景
當場景完成後 RenderView 會將完成的場景傳遞給dart:ui Window.render() 來控制GPU繪製

Platform embedding

Flutter 的引擎為具備平台未知性的,用 stable ABI (Application Binary Interface)來提供 Platform Embedder
除了六個平台以外 也能寫客製化的Platform Embedder
如在 樹莓派(Raspberry Pi) 上的遠端介面
每個平台有各自的API及限制

  • IOS 及 MacOS中
    Flutter 將如同 UIViewController 及 NSViewController 被載入到 embedder中
    Platform Embedder 將創建 FlutterEngine 提供 Dart VM及相關 Flutter Rutime 及
    FlutterViewController 的服務,其中FlutterViewController將連接到
    FlutterEnfine透過傳遞 UIKit 或 Cocoa 輸入事件,並且透過 Metal 或是 OpenGL渲染

  • Android中
    如果 Activity來載入到 embedder 中
    透過FlutterView來控制,根據布局及 z-ordering requirements 來進行渲染

  • Windows中
    Flutter host傳統的 Win32 app
    並且內容是透過 ANGLE (將 OpenGL API 轉成 DirectX 11 的程式庫) 來進行渲染
    目前也正在進行 UWP app model的 Windows embedder
    將直接透過 DirectX 12 直接調用 GPU 來取代 ANGLE

Integrating with other code

Flutter 提供多項的互動機制
可以操作由Kotlin、Swift或是原生的C語言based的API
可將原生的控制引進Flutter App內或是鑲嵌Flutter到既有的應用程式

Platform channels

Mobile及Desktop App中
Flutter 允許呼叫客製化的程式透過 Platform Channel
這可以使得App能透過 Dart Code 能與 平台限定程式碼之間通訊
透過創建一個共同的通道
我們可以在平台限定及Dart程式碼中傳遞、獲得訊息
資料可以在Dart中進行序列化(Map -> 格式化後的結果)
再到平台上進行反序列化
(Data -> HashMap (Android) or Dictionary (Swift))
structure

以下是通道建立的範例
channel

Foreign Function Interface

Dart 提供一個直接的機制binding原生程式碼透過dart:ffi
Foreign function Interface 由於不需要透過資料序列化
可被視為是更快速的平台通道 (Platform channel)
除此之外
Dart 中具備方式去向 heap 要求記憶體空間並且可以靜態或動態連結程式庫
以下為使用 FFI 的範例
FFI
Rendering native controls in a Flutter app
如果想include既有的平台componets到Flutter App中是如何處理的?
Flutter 解決這項問題透過 platform view widgets(AndroidView or UiKitView)
這將使我們能夠鑲嵌在每個平台上執行的內容至Flutter App
這邊透過 AndroidView 舉例
它主要提供了以下功能

  1. 將原生 View 所渲染的圖形材質進行複製並且呈現到 Flutter 中當每偵被繪畫過時
  2. 當受到hit testing 或 input 擷取時回應,並轉換他們成為原生平台可知曉的input操作
  3. 傳遞命令及回應在原生與Flutter 層之間
    無法避免的是,這些行為將造成一定數量的Overhead在同步行為上
    因此這個方式會比較適合在像是 Google Maps 的複雜操作上
    重新透過 Flutter去重新實作對應的操作並不是非常理想
    picture3

Hosting Flutter content in a parent app

Flutter App將被 Android activity 或 iOS 的UIViewController所託管
Flutter 內容也能夠被鑲嵌進入既有的Android 或 iOS App 透過相同的鑲嵌API
Flutter 的模組被設計成可以輕鬆的進行鑲嵌
不僅僅可以透過 Gradle 或是 XCode 來建置也可透過compile的方式做成 Android Archive 或是 iOS Framework binary來使用並且無須要求每個開發者都安裝Flutter
(當要進行部門合作時可這樣運作)
Flutter 引擎需要花費一段時間進行載入
因此為了最小化UI的延遲,最好是在所有初始化序列最前面進行Flutter 引擎的初始化
不然至少在Flutter繪製第一個畫面時進行初始化
如此一來才不會降低使用者體驗在App運作中
更多關於 Flutter 是如何載入至既有的 Android 及 iOS app可以在以下連結查看
Load sequence, performance and memory topic
Flutter Web Support
由於Dart原先設計便考量到要轉譯成JavaScript
因此當 Flutter App 需要進行轉化成 JavaScript 時便相對直接了
然而由於 Flutter 引擎是由 C++ 寫成
而其原先被設計成操縱底下OS而非Web Browser
因此便有了不同的方式去達成 Web Support
在Web中,Flutter 提供一個在基本瀏覽器之上的引擎
目前我們有兩種選項去進行Flutter內容的渲染

  1. HTML
    在此 Flutter 透過 HTML, CSS, Canvas 以及 SVG
    提供最佳的程式碼大小
  2. WebGL
    Flutter 透過 Skia 來將渲染行為進行編譯成WebAssembly,並且命名為 CanvasKit
    提供最快速的方式來提供與原生手機App較接近的圖像保真度
    以下為架構圖

在開發階段
Flutter web 透過 dartdevc 來支援熱重啟(並非熱重載)
dartdevc
為支援增量編譯的編譯器
而當準備好要推出Web產品時
dart2js則會派上用場

dart2js

為高優化過的JavaScript編譯器
可將 Flutter程式碼轉成壓縮過後的原始檔案
並且可被佈署至任意的 Web Server
並且能過透過deferred imports來提供單一檔案或拆成多個檔案
deferred imports
也可視為 lazy loading 僅在dart2js使用
開發時透過Dart VM可能看不出效果
Further information
關於 Flutter 的更多細節可從 Inside Flutter
來了解相關的指南及該框架的設計哲學

參考資料

強而有力的官方網站


上一篇
通徹 Flutter 學習路徑 Day 02 - Flutter Cli 介紹
下一篇
通徹 Flutter 學習路徑 Day 04 - 常用 Widgets 大全
系列文
通徹 Flutter 學習路徑30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言